use std;
use std::vec::Vec;
use std::default::{Default};
use geom::{self, Len, Off, Pos, Dim, AABB};
use diff;
use tree;
use sdl2;
use sdl2::pixels::{Color};

/// Represents a element to be drawn onto the screen. A [`Fragment`] may be a simple element such as
/// a rectangle or some text, or a compound element comprised of sub-fragments. Fragments do not
/// typically have an absolute position; rather, their position will be determined in relation to
/// fragments around them.
#[derive(Clone)]
pub struct Frag {
  calculated_offset: Pos,
  calculated_dim: Dim,
  pub info: FragInfo,
  typ: FragType,
}

#[derive(Clone)]
enum FragType {
  Atomic(AtomicFragType),
  Compound {
    spine: FragSpine,
    subfrags: Vec<Frag>,
  },
}

#[derive(Clone)]
enum AtomicFragType {
  Empty,
  Fill(Color),
}

/// Types of "spines" for compound fragments; decides how sub-fragments will be placed in relation to
/// eachother.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum FragSpine { PackLR, PackRL, PackTB, PackBT, Overlay, Container }

#[derive(Copy, Clone, Eq, PartialEq)]
pub struct FragInfo {
  pub bounds: Dim, // only applicable for FragSpine::Container
  pub align_h: AlignH,
  pub align_v: AlignV,
}

// Alignment options (horizontal and vertical, respectively).
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum AlignH { Left, Center, Right }
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum AlignV { Top, Middle, Bottom }

////////////////////////////////////////////////////////////////////////////////////////////////////
// Simple constructors

impl Default for AlignH {
  fn default() -> Self { AlignH::Left }
}

impl Default for AlignV {
  fn default() -> Self { AlignV::Middle }
}

impl Default for FragInfo {
  fn default() -> Self {
    FragInfo {
      bounds: geom::MAX_DIM,
      align_h: AlignH::default(),
      align_v: AlignV::default(),
    }
  }
}

impl FragInfo {
  pub fn with_bounds(bounds: Dim) -> Self {
    let mut i = FragInfo::default();
    i.bounds = bounds;
    i
  }

  pub fn with_align(align_h: AlignH, align_v: AlignV) -> Self {
    let mut i = FragInfo::default();
    i.align_h = align_h;
    i.align_v = align_v;
    i
  }
}

impl Default for Frag {
  fn default() -> Self {
    AtomicFragType::Empty.into()
  }
}

impl From<FragType> for Frag {
  fn from(typ: FragType) -> Frag {
    Frag {
      calculated_offset: geom::ORIGIN,
      calculated_dim: geom::EMPTY,
      info: FragInfo::default(),
      typ,
    }
  }
}

impl From<AtomicFragType> for Frag {
  fn from(typ: AtomicFragType) -> Frag {
    FragType::Atomic(typ).into()
  }
}

impl Frag {
  /// Construct a compound fragment with the given spine and sub-fragments. The "spine" determines
  /// how each sub-fragment will be placed in relation to eachother. It is recommended that you use
  /// the constructor functions such as `pack_lr()`, `pack_tb()`, `overlay()` rather than calling
  /// this function. See those functions for more specific details about the different spines.
  pub fn compound(spine: FragSpine, subfrags: Vec<Frag>) -> Frag {
    FragType::Compound {
      spine,
      subfrags,
    }.into()
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Combinators

/// Returns a fragment no appearance, that takes up no space.
pub fn empty() -> Frag {
  AtomicFragType::Empty.into()
}

/// Returns a fragment which will take up as much area as possible, filling it with the given color.
pub fn fill(color: Color) -> Frag {
  AtomicFragType::Fill(color).into()
}

/// Returns a fragment which is a solid rectangle with the given size and color.
pub fn rect(dim: Dim, color: Color) -> Frag {
  fill(color)
    .container()
    .bounds(dim)
}

/// Returns a horizontally-packed fragment composed of the given fragments. Each fragment (chosen
/// from left to right) will greedily take up as much horizonal space as possible.
///
///   let fill = Color::RGB(255, 0, 0);
///   let mut scene = pack_lr(vec![rect(Dim(4, 4), fill), rect(Dim(4, 4), fill)]);
///   assert!(scene.calculate_geometry(Dim(10, 10)).dim(), Dim(8, 4));
///
pub fn pack_lr(frgs: Vec<Frag>) -> Frag {
  Frag::compound(FragSpine::PackLR, frgs)
}

/// Like `pack_lr`, but fragment size are calculated from right to left.
pub fn pack_rl(frgs: Vec<Frag>) -> Frag {
  Frag::compound(FragSpine::PackRL, frgs)
}

/// Returns a verticaly-packed fragment composed of the given fragments. Each fragment (chosen
/// from top to bottom) will greedily take up as much horizonal space as possible.
///
///   let fill = Color::RGB(255, 0, 0);
///   let mut scene = pack_tb(vec![rect(Dim(4, 4), fill), rect(Dim(4, 4), fill)]);
///   assert!(scene.calculate_geometry(Dim(10, 10)).dim(), Dim(4, 8));
///
pub fn pack_tb(frgs: Vec<Frag>) -> Frag {
  Frag::compound(FragSpine::PackTB, frgs)
}

/// Like `pack_tb`, but fragment size are calculated from bottom to top.
pub fn pack_bt(frgs: Vec<Frag>) -> Frag {
  Frag::compound(FragSpine::PackBT, frgs)
}

/// Returns a fragment composed of the given fragments overlayed on top of eachother. Each fragment
/// will take up as much space as possible, and will be drawn starting from the first fragment to
/// the last fragment. The total area taken up by this fragment will be the area taken up by the
/// largest sub-fragment.
///
///   let fill = Color::RGB(255, 0, 0);
///   let mut scene = overlay(vec![rect(Dim(4, 4), fill), rect(Dim(4, 4), fill)]);
///   assert!(scene.calculate_geometry(Dim(10, 10)).dim(), Dim(4, 4));
///
pub fn overlay(frgs: Vec<Frag>) -> Frag {
  Frag::compound(FragSpine::Overlay, frgs)
}

/// Returns a "container" fragment composed of the given fragments. A container will try take up a
/// specific amount of space, regardless of the size of the fragments contained within it. By
/// default, a container will try to take up as much space as possible, but its size can be
/// configured using the `.bounds()`, `.width()` and `.height()` functions.
///
///   let fill = Color::RGB(255, 0, 0);
///   let mut scene = container(vec![rect(Dim(4, 4), fill)]);
///   assert!(scene.calculate_geometry(Dim(10, 10)).dim(), Dim(10, 10));
///   scene = scene.width(5);
///   assert!(scene.calculate_geometry(Dim(10, 10)).dim(), Dim(5, 10));
///   scene = scene.height(3);
///   assert!(scene.calculate_geometry(Dim(10, 10)).dim(), Dim(5, 3));
///
/// A container will draw its sub-fragments on top of eachother, like in `.overlay()`.
pub fn container(frgs: Vec<Frag>) -> Frag {
  Frag::compound(FragSpine::Container, frgs)
}

impl Frag {

  /// Creates a container fragment composed of just this fragment.
  pub fn container(self) -> Frag {
    container(vec![self])
  }

  /// Create an overlay fragment where the given fragment is above this one.
  pub fn overlay(self, other: Frag) -> Frag {
    overlay(vec![self, other])
  }

  /// Create an overlay fragment where the given fragment is below this one.
  pub fn underlay(self, other: Frag) -> Frag {
    overlay(vec![other, self])
  }

  /// All fragments have an "alignment", which determines where they will be placed in a large
  /// region.  The default alignment is `AlignH::Left`, `AlignV::Middle`. Alignment can be
  /// configured using the `align` function.
  ///
  ///   let fill = Color::RGB(255, 0, 0);
  ///   let mut scene = rect(Dim(4, 4), fill);
  ///   assert!(scene.calculate_geometry(Dim(10, 10)).pos(), Pos(0, 3));
  ///   scene = scene.align(AlignH::Center, AlignV::Bottom);
  ///   assert!(scene.calculate_geometry(Dim(10, 10)).pos(), Pos(3, 6));
  ///
  pub fn align(mut self, align_h: AlignH, align_v: AlignV) -> Frag {
    self.info.align_h = align_h;
    self.info.align_v = align_v;
    self
  }

  /// Like `.align()`, but only configures horizontal alignment.
  pub fn align_h(mut self, align_h: AlignH) -> Frag {
    self.info.align_h = align_h;
    self
  }

  /// Like `.align()`, but only configures vertical alignment.
  pub fn align_v(mut self, align_v: AlignV) -> Frag {
    self.info.align_v = align_v;
    self
  }

  /// Configures the size of a container or rectangle fragments. For other fragments, this does
  /// nothing.
  ///
  ///   let fill = Color::RGB(255, 0, 0);
  ///   let mut scene = rect(Dim(4, 4), fill);
  ///   assert!(scene.calculate_geometry(Dim(10, 10)).dim(), Dim(4, 4));
  ///   scene = scene.bounds(Dim(6, 5));
  ///   assert!(scene.calculate_geometry(Dim(10, 10)).dim(), Dim(6, 5));
  ///
  /// In order to impose size restrictions on other kinds of fragments, first place them in
  /// a container with `.container()`.
  pub fn bounds(self, dim: Dim) -> Frag { self.on_bounds(|_| dim) }

  /// Like `.bounds()`, but only configures the width of the component.
  pub fn width(self, w: Len) -> Frag { self.on_bounds(|Dim(_, h)| Dim(w, h)) }

  /// Like `.bounds()`, but only configures the height of the component.
  pub fn height(self, h: Len) -> Frag { self.on_bounds(|Dim(w, _)| Dim(w, h)) }

  fn on_bounds<F>(mut self, f: F) -> Frag
  where F: FnOnce(Dim) -> Dim {
    self.info.bounds = f(self.info.bounds);
    self
  }
}

impl FragType {
  fn subfrags(&self) -> &[Frag] {
    match *self {
      FragType::Compound { ref subfrags, .. } => subfrags,
      FragType::Atomic(_) => &[],
    }
  }

  fn subfrags_mut(&mut self) -> Option<&mut Vec<Frag>> {
    match *self {
      FragType::Compound { ref mut subfrags, .. } => Some(subfrags),
      FragType::Atomic(_) => None,
    }
  }
}


//////////////////////////////////////////////////////////////////////////////////////////
// Calculating geometry

impl Frag {

  /// Recalculate's the position and dimension of this fragment (and all sub-fragments) if contained
  /// in a region of the specified size. Returns the axis-aligned bounding-box encompassing this
  /// fragment.
  pub fn calculate_geometry(&mut self, dim: Dim) -> AABB {
    self.measure(dim);
    self.position(dim);
    AABB::new(self.calculated_offset, self.calculated_dim)
  }

  fn measure(&mut self, max_dim: Dim) {
    self.calculated_offset = geom::ORIGIN;
    self.calculated_dim = self.typ.measure(max_dim, &self.info);
  }

  fn position(&mut self, outer_dim: Dim) {
    self.calculated_offset.0 = match self.info.align_h {
      AlignH::Left => 0,
      AlignH::Right => (outer_dim.0 - self.calculated_dim.0) as Off,
      AlignH::Center => ((outer_dim.0 - self.calculated_dim.0) / 2) as Off,
    };
    self.calculated_offset.1 = match self.info.align_v {
      AlignV::Top => 0,
      AlignV::Bottom => (outer_dim.1 - self.calculated_dim.1) as Off,
      AlignV::Middle => ((outer_dim.1 - self.calculated_dim.1) / 2) as Off,
    };
  }
}

impl FragType {
  fn measure(&mut self, max_dim: Dim, info: &FragInfo) -> Dim {
    match *self {
      FragType::Atomic(AtomicFragType::Empty) =>
        geom::EMPTY,
      FragType::Atomic(AtomicFragType::Fill(_)) =>
        max_dim,
      FragType::Compound { spine, ref mut subfrags } =>
        spine.measure(max_dim, info, subfrags),
    }
  }
}

impl FragSpine {
  fn measure(self, mut max_dim: Dim, info: &FragInfo, frags: &mut [Frag]) -> Dim {
    match self {
      FragSpine::PackLR |
      FragSpine::PackRL => {
        let (w, h) = pack(
          frags, 0, max_dim.0, self == FragSpine::PackRL,
          |frg| frg.calculated_dim.0,
          |frg| frg.calculated_dim.1,
          |frg, rem_w| frg.measure(Dim(rem_w, max_dim.1)),
          |frg, x, w, h| { frg.position(Dim(w, h)); frg.calculated_offset.0 += x; }
        );
        Dim(w, h)
      }

      FragSpine::PackTB |
      FragSpine::PackBT => {
        let (h, w) = pack(
          frags, 0, max_dim.1, self == FragSpine::PackBT,
          |frg| frg.calculated_dim.1,
          |frg| frg.calculated_dim.0,
          |frg, rem_h| frg.measure(Dim(max_dim.0, rem_h)),
          |frg, y, h, w| { frg.position(Dim(w, h)); frg.calculated_offset.1 += y; }
        );
        Dim(w, h)
      }

      FragSpine::Overlay |
      FragSpine::Container => {
        if self == FragSpine::Container {
          max_dim = Dim::min(max_dim, info.bounds);
        }

        let mut min_dim = Dim(0, 0);
        for frg in frags.iter_mut() {
          frg.measure(max_dim);
          min_dim = Dim::max(min_dim, frg.calculated_dim);
        }
        for frg in frags.iter_mut() {
          frg.position(min_dim);
        }

        if self == FragSpine::Container {
          max_dim
        } else {
          min_dim
        }
      }
    }
  }
}

// General "packing" algorithm, shared between PackH and PackV.
fn pack<X, F0, F1, FM, FP>(xs: &mut [X],
                           spacing: Off, space: Len, backwards: bool,
                           get_len0: F0, get_len1: F1,
                           measure: FM, position: FP) -> (Len, Len)
where F0: Fn(&X) -> Len,
      F1: Fn(&X) -> Len,
      FM: Fn(&mut X, Len) -> (),
      FP: Fn(&mut X, Off, Len, Len) -> (),
{
  let total_spacing = (spacing as Len) * (xs.len().saturating_sub(1) as Len);
  let mut sum0 = 0;
  let mut max1 = 0;

  // TODO: find a better way than this
  if backwards { xs.reverse() }

  for x in xs.iter_mut() {
    let remain0 = (space - sum0).saturating_sub(total_spacing);
    measure(x, remain0);
    sum0 += get_len0(x);
    max1 = std::cmp::max(max1, get_len1(x));
  }

  if backwards { xs.reverse() }

  let mut offset0 = 0;
  for x in xs.iter_mut() {
    let len0 = get_len0(x);
    position(x, offset0, len0, max1);
    offset0 += len0 as Off + spacing;
  }

  (sum0 + total_spacing, max1)
}


////////////////////////////////////////////////////////////////////////////////////////////////////
// Tree operations

impl tree::Underlying for Frag {
  type Spine = FragSpine;
  type Info = FragInfo;

  fn create(spine: FragSpine, info: FragInfo, children: Vec<Frag>) -> Frag {
    let mut f = Frag::compound(spine, children);
    f.info = info;
    f
  }

  fn set_spine(&mut self, spine: FragSpine) {
    match self.typ {
      FragType::Atomic(_) =>
        self.typ = FragType::Compound {
          spine,
          subfrags: vec![],
        },

      FragType::Compound { spine: ref mut cur_spine, .. } =>
        *cur_spine = spine,
    }
  }

  fn set_info(&mut self, info: FragInfo) {
    self.info = info;
  }
}

impl std::ops::Index<usize> for Frag {
  type Output = Frag;
  fn index(&self, i: usize) -> &Frag {
    &self.typ.subfrags()[i]
  }
}

impl std::ops::IndexMut<usize> for Frag {
  fn index_mut(&mut self, i: usize) -> &mut Frag {
    &mut self.typ.subfrags_mut().expect("fragment has no sub-fragments")[i]
  }
}

impl diff::ApplyPatch<Frag> for Frag {
  fn len(&self) -> usize {
    self.typ.subfrags().len()
  }

  fn remove(&mut self, i: usize) {
    self.typ.subfrags_mut()
            .map(|frgs| frgs.remove(i));
  }

  fn insert(&mut self, i: usize, frg: Frag) {
    self.typ.subfrags_mut()
            .map(|frgs| frgs.insert(i, frg));
  }

  fn overwrite(&mut self, i: usize, frg: Frag) -> bool {
    self.typ.subfrags_mut()
            .map(|frgs| frgs[i] = frg)
            .is_some()
  }
}

#[cfg(test)]
mod test_tree {
  use super::*;
  use sdl2::pixels::{Color};
  use tree::{Underlying};

  #[test]
  fn test_create_1() {
    // Create with RL (right->left) spine
    let f = pack_rl(vec![]);
    match f.typ {
      FragType::Compound { spine, ref subfrags } =>
        assert_eq!((spine, subfrags.len()), (FragSpine::PackRL, 0)),
      _ =>
        assert!(false),
    }
  }

  #[test]
  fn test_create_2() {
    // Packed with 1 child
    let mut f1 = pack_lr(vec![fill(Color::RGB(255, 0, 0))]);
    // Change spine
    f1.set_spine(FragSpine::PackTB);
    match f1.typ {
      FragType::Compound { spine, ref subfrags } => {
        assert_eq!((spine, subfrags.len()), (FragSpine::PackTB, 1));
        match subfrags[0].typ {
          FragType::Atomic(AtomicFragType::Fill(_)) => (),
          _ => assert!(false),
        }
      }
      _ => assert!(false),
    }
  }
}


//////////////////////////////////////////////////////////////////////////////////////////
// Rendering

/// The `DrawFrag` trait represents a canvas that is able to draw arbitrary fragments.
/// Implementors must provide several low-level operations for drawing basic shapes. The
/// auto-derived method `draw_frag` displays the given fragment.
pub trait DrawFrag {

  /// Draw the given fragment onto this canvas, relative to the top-left corner.
  fn draw_frag(&mut self, frg: &Frag) {
    frg.draw_offset(self, geom::ORIGIN)
  }

  /// Draw the given fragment onto this canvas, relative to the given position.
  fn draw_frag_offset(&mut self, frg: &Frag, pos: Pos) {
    frg.draw_offset(self, pos)
  }

  /// Draw a axis-align bounding-box fill the specified color.
  fn fill_aabb(&mut self, aabb: AABB, color: Color);
}

impl Frag {
  /// Draw this fragment at the given offset.
  fn draw_offset<C>(&self, c: &mut C, mut pos: Pos)
  where C: DrawFrag + ?Sized {
    // This while loop is used to perform tail-call elimination. See the longer comment below.
    let mut next: Option<&Frag> = Some(self);
    while let Some(frg) = next {
      next = None;
      pos.0 += frg.calculated_offset.0;
      pos.1 += frg.calculated_offset.1;

      match frg.typ {
        FragType::Atomic(ref atom_typ) =>
          atom_typ.draw_offset(c, pos, frg.calculated_dim),

        FragType::Compound { ref subfrags, .. } =>
          for subfrg in subfrags.iter() {
            // Only recursively draw sub-frags that are not the last; this way, the last fragment
            // will be handled by the outer 'while' loop, essentially performing manual tail-call
            // elimination. This is crucial to prevent huge stack-frame build up when there are many
            // compound fragments with only one sub-fragment.
            if let Some(frg_rec) = next {
              frg_rec.draw_offset(c, pos);
            }
            next = Some(subfrg);
          },
      }
    }
  }
}

impl AtomicFragType {
  fn draw_offset<C>(&self, c: &mut C, pos: Pos, dim: Dim)
  where C: DrawFrag + ?Sized {
    match *self {
      AtomicFragType::Empty =>
        (),
      AtomicFragType::Fill(color) =>
        c.fill_aabb(AABB::new(pos, dim), color),
    }
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////

impl<T> DrawFrag for sdl2::render::Canvas<T>
where T: sdl2::render::RenderTarget {
  fn fill_aabb(&mut self, aabb: AABB, color: Color) {
    self.set_draw_color(color);
    self.fill_rect(Some(aabb.into())).unwrap();
  }
}
